
// Copyright (C) 2011-2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;

namespace Derm.Render
{
	/// <summary>
	/// Class describing a vertex array buffer item.
	/// </summary>
	public class ArrayBufferItem : IEquatable<ArrayBufferItem>
	{
		#region Constructors

		/// <summary>
		/// Construct an ArrayBufferItem from a <see cref="ArrayBufferItemAttribute"/>.
		/// </summary>
		/// <param name="attribute">
		/// A <see cref="ArrayBufferItemAttribute"/> describing the vertex array buffer item.
		/// </param>
		public ArrayBufferItem(ArrayBufferItemAttribute attribute)
		{
			if (attribute == null)
				throw new ArgumentNullException("attribute");

			mVertexArrayType = GetArrayType(attribute.ArrayBaseType, attribute.ArrayLength);
		}

		/// <summary>
		/// Construct an ArrayBufferItem from a <see cref="VertexArrayType"/>.
		/// </summary>
		/// <param name="vertexArrayType">
		/// A <see cref="VertexArrayType"/> that synthetize all informations about a vertex array buffer item.
		/// </param>
		public ArrayBufferItem(VertexArrayType vertexArrayType)
		{
			mVertexArrayType = vertexArrayType;
		}

		#endregion

		#region Array Information

		/// <summary>
		/// The base type of each component of the vertex array buffer item.
		/// </summary>
		public VertexBaseType BaseType { get { return (GetArrayBaseType(mVertexArrayType)); } }

		/// <summary>
		/// The type of this vertex array buffer item.
		/// </summary>
		public VertexArrayType Type
		{
			get { return (mVertexArrayType); }
		}

		/// <summary>
		/// The number of components of the vertex array buffer item.
		/// </summary>
		public uint ArrayLength { get { return (GetArrayLength(mVertexArrayType)); } }

		/// <summary>
		/// Determine whether the base type is a floating-point value.
		/// </summary>
		public bool IsFloat { get { return (IsFloatBaseType(mVertexArrayType)); } }

		/// <summary>
		/// The almost compact information about a vertex array buffer item.
		/// </summary>
		private readonly VertexArrayType mVertexArrayType;

		#endregion

		#region Array Item Normalization

		/// <summary>
		/// Determine whether the integer values shall be considered normalized floating-point.
		/// </summary>
		public bool Normalized
		{
			get { return (mNormalized); }
			set { mNormalized = value; }
		}

		/// <summary>
		/// Determine whether the integer values shall be considered normalized floating-point.
		/// </summary>
		private bool mNormalized;

		#endregion

		#region Static Utilities

		#region Get Array Type

		/// <summary>
		/// Get the corresponding <see cref="VertexArrayType"/> from a set of parameters.
		/// </summary>
		/// <param name="baseType">
		/// A <see cref="VertexBaseType"/> indicating the type of the components of the vertex array buffer item.
		/// </param>
		/// <param name="length">
		/// A <see cref="System.UInt32"/> indicating the length of the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayType"/> having a base type equals to <paramref name="baseType"/>, a
		/// length equals to <paramref name="length"/> and a rank equals to 1 (implicit).
		/// </returns>
		public static VertexArrayType GetArrayType(VertexBaseType baseType, uint length)
		{
			return (GetArrayType(baseType, length, 1));
		}

		/// <summary>
		/// Get the corresponding <see cref="VertexArrayType"/> from a set of parameters.
		/// </summary>
		/// <param name="baseType">
		/// A <see cref="VertexBaseType"/> indicating the type of the components of the vertex array buffer item.
		/// </param>
		/// <param name="length">
		/// A <see cref="System.UInt32"/> indicating the length of the vertex array buffer item.
		/// </param>
		/// <param name="rank">
		/// A <see cref="System.UInt32"/> indicating the rank of the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayType"/> having a base type equals to <paramref name="baseType"/>, a
		/// length equals to <paramref name="length"/> and a rank equals to <paramref name="rank"/>.
		/// </returns>
		public static VertexArrayType GetArrayType(VertexBaseType baseType, uint length, uint rank)
		{
			if ((length < 1) || (length > 4))
				throw new ArgumentException("out of range", "length");
			if ((rank < 1) || (rank > 4))
				throw new ArgumentException("out of range", "rank");
			if ((rank > 1) && (length == 1))
				throw new ArgumentException("bad combination", "length");

			switch (baseType) {

				#region VertexBaseType.Float

				case VertexBaseType.Float:
					switch (rank) {
						case 1:
							switch (length) {
								case 1:
									return (VertexArrayType.Float);
								case 2:
									return (VertexArrayType.Vec2);
								case 3:
									return (VertexArrayType.Vec3);
								case 4:
									return (VertexArrayType.Vec4);
								default:
									throw new NotSupportedException();
							}
						case 2:
							switch (length) {
								case 2:
									return (VertexArrayType.Mat2x2);
								case 3:
									return (VertexArrayType.Mat2x3);
								case 4:
									return (VertexArrayType.Mat2x4);
								default:
									throw new NotSupportedException();
							}
						case 3:
							switch (length) {
								case 2:
									return (VertexArrayType.Mat3x2);
								case 3:
									return (VertexArrayType.Mat3x3);
								case 4:
									return (VertexArrayType.Mat3x4);
								default:
									throw new NotSupportedException();
							}
						case 4:
							switch (length) {
								case 2:
									return (VertexArrayType.Mat4x2);
								case 3:
									return (VertexArrayType.Mat4x3);
								case 4:
									return (VertexArrayType.Mat4x4);
								default:
									throw new NotSupportedException();
							}
						default:
							throw new NotSupportedException();
					}

				#endregion

				default:
					throw new NotSupportedException("unsupported vertex array type of " + baseType);
			}
		}

		/// <summary>
		/// Get the corresponding <see cref="VertexArrayType"/> from a <see cref="Type"/>.
		/// </summary>
		/// <param name="type">
		/// A <see cref="Type"/> indicating the structure describing the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayType"/> corresponding to <paramref name="type"/>.
		/// </returns>
		public static VertexArrayType GetArrayType(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			if (type == typeof(Single))
				return (VertexArrayType.Float);
			if (type == typeof(Int32))
				return (VertexArrayType.Int);
			if (type == typeof(UInt32))
				return (VertexArrayType.UInt);

			ArrayBufferItemAttribute attribute = (ArrayBufferItemAttribute) Attribute.GetCustomAttribute(type, typeof(ArrayBufferItemAttribute));
			if (attribute != null)
				return (attribute.ArrayType);

			throw new ArgumentException(String.Format("unable to match type {0}", type.Name));
		}

		#endregion

		#region Get Array Base Type

		/// <summary>
		/// Get the array components base type of the vertex array buffer item.
		/// </summary>
		/// <param name="vertexArrayType">
		/// A <see cref="VertexArrayType"/> that describe the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexBaseType"/> indicating  the type of the components of
		/// the vertex array buffer item.
		/// </returns>
		public static VertexBaseType GetArrayBaseType(VertexArrayType vertexArrayType)
		{
			switch (vertexArrayType) {
				case VertexArrayType.Float:
				case VertexArrayType.Vec2:
				case VertexArrayType.Vec3:
				case VertexArrayType.Vec4:
				case VertexArrayType.Mat2x2:
				case VertexArrayType.Mat2x3:
				case VertexArrayType.Mat2x4:
				case VertexArrayType.Mat3x2:
				case VertexArrayType.Mat3x3:
				case VertexArrayType.Mat3x4:
				case VertexArrayType.Mat4x2:
				case VertexArrayType.Mat4x3:
				case VertexArrayType.Mat4x4:
					return (VertexBaseType.Float);
				case VertexArrayType.Double:
				case VertexArrayType.DoubleVec2:
				case VertexArrayType.DoubleVec3:
				case VertexArrayType.DoubleVec4:
				case VertexArrayType.DoubleMat2x2:
				case VertexArrayType.DoubleMat2x3:
				case VertexArrayType.DoubleMat2x4:
				case VertexArrayType.DoubleMat3x2:
				case VertexArrayType.DoubleMat3x3:
				case VertexArrayType.DoubleMat3x4:
				case VertexArrayType.DoubleMat4x2:
				case VertexArrayType.DoubleMat4x3:
				case VertexArrayType.DoubleMat4x4:
					return (VertexBaseType.Double);
				case VertexArrayType.Int:
				case VertexArrayType.IntVec2:
				case VertexArrayType.IntVec3:
				case VertexArrayType.IntVec4:
					return (VertexBaseType.Int);
				case VertexArrayType.UIntVec2:
				case VertexArrayType.UIntVec3:
				case VertexArrayType.UIntVec4:
					return (VertexBaseType.UInt);
				default:
					throw new NotSupportedException("unsupported vertex array base type of " + vertexArrayType);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public static VertexBaseType GetArrayBaseType(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			if (type == typeof(Single))
				return (VertexBaseType.Float);
			if (type == typeof(HalfFloat))
				return (VertexBaseType.Half);
			if (type == typeof(Int32))
				return (VertexBaseType.Int);
			if (type == typeof(UInt32))
				return (VertexBaseType.UInt);
			if (type == typeof(Int16))
				return (VertexBaseType.Short);
			if (type == typeof(UInt16))
				return (VertexBaseType.UShort);
			if (type == typeof(Byte))
				return (VertexBaseType.UByte);
			if (type == typeof(SByte))
				return (VertexBaseType.Byte);

			throw new ArgumentException(String.Format("unable to match type {0}", type.Name));
		}

		#endregion

		#region Is Float

		/// <summary>
		/// Determine whether a base type is a floating-point value.
		/// </summary>
		public static bool IsFloatBaseType(VertexBaseType vertexBaseType)
		{
			switch (vertexBaseType) {
					case VertexBaseType.Float:
					case VertexBaseType.Half:
					case VertexBaseType.Double:
						return (true);
					default:
						return (false);
				}
		}

		/// <summary>
		/// Determine whether a vertex array type is composed by floating-point value(s).
		/// </summary>
		public static bool IsFloatBaseType(VertexArrayType vertexArrayType)
		{
			return (IsFloatBaseType(GetArrayBaseType(vertexArrayType)));
		}

		#endregion

		#region Array Length & Rank

		/// <summary>
		/// Get the number of components of the vertex array buffer item.
		/// </summary>
		/// <param name="vertexArrayType">
		/// A <see cref="VertexArrayType"/> that describe the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns the count of the components of the vertex array buffer item. It will be a value
		/// from 1 (inclusive) to 4 (inclusive). For matrices, this value indicates the matrix height (column-major order).
		/// </returns>
		public static uint GetArrayLength(VertexArrayType vertexArrayType)
		{
			switch (vertexArrayType) {
				case VertexArrayType.Float:
				case VertexArrayType.Double:
				case VertexArrayType.Int:
					return (1);
				case VertexArrayType.Vec2:
				case VertexArrayType.DoubleVec2:
				case VertexArrayType.IntVec2:
				case VertexArrayType.UIntVec2:
				case VertexArrayType.Mat2x2:
				case VertexArrayType.Mat2x3:
				case VertexArrayType.Mat2x4:
				case VertexArrayType.DoubleMat2x2:
				case VertexArrayType.DoubleMat2x3:
				case VertexArrayType.DoubleMat2x4:
					return (2);
				case VertexArrayType.Vec3:
				case VertexArrayType.DoubleVec3:
				case VertexArrayType.IntVec3:
				case VertexArrayType.UIntVec3:
				case VertexArrayType.Mat3x2:
				case VertexArrayType.Mat3x3:
				case VertexArrayType.Mat3x4:
				case VertexArrayType.DoubleMat3x2:
				case VertexArrayType.DoubleMat3x3:
				case VertexArrayType.DoubleMat3x4:
					return (3);
				case VertexArrayType.Vec4:
				case VertexArrayType.DoubleVec4:
				case VertexArrayType.IntVec4:
				case VertexArrayType.UIntVec4:
				case VertexArrayType.Mat4x2:
				case VertexArrayType.Mat4x3:
				case VertexArrayType.Mat4x4:
				case VertexArrayType.DoubleMat4x2:
				case VertexArrayType.DoubleMat4x3:
				case VertexArrayType.DoubleMat4x4:
					return (4);
				default:
					throw new NotSupportedException("unsupported vertex array length of " + vertexArrayType);
			}
		}

		/// <summary>
		/// Get the rank of the vertex array buffer item (that is, the number of <i>vec4</i> attributes requires).
		/// </summary>
		/// <param name="vertexArrayType">
		/// A <see cref="VertexArrayType"/> that describe the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns the rank of the vertex array buffer item. It will be a value
		/// from 1 (inclusive) to 4 (inclusive). For matrices, this value indicates the matrix width (column-major order),
		/// while for simpler types the value will be 1.
		/// </returns>
		public static uint GetArrayRank(VertexArrayType vertexArrayType)
		{
			switch (vertexArrayType) {
				case VertexArrayType.Mat2x2:
				case VertexArrayType.Mat3x2:
				case VertexArrayType.Mat4x2:
				case VertexArrayType.DoubleMat2x2:
				case VertexArrayType.DoubleMat3x2:
				case VertexArrayType.DoubleMat4x2:
					return (2);
				case VertexArrayType.Mat2x3:
				case VertexArrayType.Mat3x3:
				case VertexArrayType.Mat4x3:
				case VertexArrayType.DoubleMat2x3:
				case VertexArrayType.DoubleMat3x3:
				case VertexArrayType.DoubleMat4x3:
					return (4);
				case VertexArrayType.Mat2x4:
				case VertexArrayType.Mat3x4:
				case VertexArrayType.Mat4x4:
				case VertexArrayType.DoubleMat2x4:
				case VertexArrayType.DoubleMat3x4:
				case VertexArrayType.DoubleMat4x4:
					return (4);
				case VertexArrayType.Float:
				case VertexArrayType.Double:
				case VertexArrayType.Int:
				case VertexArrayType.Vec2:
				case VertexArrayType.DoubleVec2:
				case VertexArrayType.IntVec2:
				case VertexArrayType.UIntVec2:
				case VertexArrayType.Vec3:
				case VertexArrayType.DoubleVec3:
				case VertexArrayType.IntVec3:
				case VertexArrayType.UIntVec3:
				case VertexArrayType.Vec4:
				case VertexArrayType.DoubleVec4:
				case VertexArrayType.IntVec4:
				case VertexArrayType.UIntVec4:
					return (1);
				default:
					throw new NotSupportedException("unsupported vertex array rank of " + vertexArrayType);
			}
		}

		public static bool IsArraySimpleType(VertexArrayType vertexArrayType)
		{
			return ((GetArrayLength(vertexArrayType) == 1) && (GetArrayRank(vertexArrayType) == 1));
		}

		public static bool IsArrayVectorType(VertexArrayType vertexArrayType)
		{
			return ((GetArrayLength(vertexArrayType) > 1) && (GetArrayRank(vertexArrayType) == 1));
		}

		public static bool IsArrayMatrixType(VertexArrayType vertexArrayType)
		{
			return (GetArrayRank(vertexArrayType) > 1);
		}

		#endregion

		#region Array Size

		/// <summary>
		/// Get the size of a vertex array buffer item.
		/// </summary>
		/// <param name="vertexArrayType">
		/// A <see cref="VertexArrayType"/> that describe the vertex array buffer item.
		/// </param>
		/// <returns>
		/// It returns the size of the vertex array buffer type having the type <paramref name="vertexArrayType"/>, in bytes.
		/// </returns>
		public static uint GetArrayItemSize(VertexArrayType vertexArrayType)
		{
			uint baseTypeSize;

			switch (GetArrayBaseType(vertexArrayType)) {
				case VertexBaseType.Byte:
				case VertexBaseType.UByte:
					baseTypeSize = 1;
					break;
				case VertexBaseType.Short:
				case VertexBaseType.UShort:
				case VertexBaseType.Half:
					baseTypeSize = 2;
					break;
				case VertexBaseType.Float:
				case VertexBaseType.Int:
				case VertexBaseType.UInt:
					baseTypeSize = 4;
					break;
				case VertexBaseType.Double:
					baseTypeSize = 8;
					break;
				default:
					throw new NotSupportedException("base type not supported");
			}

			uint length = GetArrayLength(vertexArrayType);
			uint rank = GetArrayRank(vertexArrayType);

			return (baseTypeSize * length * rank);
		}

		#endregion

		#endregion

		#region Equality Operators

		public static bool operator==(ArrayBufferItem left, ArrayBufferItem right)
		{
			if (ReferenceEquals(left, right))
				return (true);
			if (ReferenceEquals(left, null))
				return (false);

			return (left.Equals(right));
		}

		public static bool operator!=(ArrayBufferItem left, ArrayBufferItem right)
		{
			if (ReferenceEquals(left, right))
				return (false);
			if (ReferenceEquals(left, null))
				return (false);

			return (!left.Equals(right));
		}

		#endregion

		#region IEquatable<ArrayBufferItem> Implementation

		/// <summary>
		/// Indicates whether the this Matrix is equal to another Matrix.
		/// </summary>
		/// <param name="other">
		/// A Matrix to compare with this object.
		/// </param>
		/// <returns>
		/// It returns true if the this Matrix is equal to <paramref name="other"/>; otherwise, false.
		/// </returns>
		public bool Equals(ArrayBufferItem other)
		{
			if (ReferenceEquals(null, other))
				return false;
			
			if (BaseType != other.BaseType)
				return (false);
			if (ArrayLength != other.ArrayLength)
				return (false);
			if (Normalized != other.Normalized)
				return (false);

			return (true);
		}

		/// <summary>
		/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <param name="obj">
		/// The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.
		/// </param>
		/// <returns>
		/// It returns true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
		/// </returns>
		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj))
				return false;
			if (obj.GetType() != typeof(IVertex3))
				return false;

			return (Equals((ArrayBufferItem)obj));
		}

		/// <summary>
		/// Serves as a hash function for a particular type. <see cref="M:System.Object.GetHashCode"/> is suitable for
		/// use in hashing algorithms and data structures like a hash table.
		/// </summary>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"/>.
		/// </returns>
		public override int GetHashCode()
		{
			unchecked {
				int result;

				result = BaseType.GetHashCode();
				result = (result * 397) ^ ArrayLength.GetHashCode();
				result = (result * 397) ^ Normalized.GetHashCode();

				return result;
			}
		}

		#endregion
	}
}
